
package com.tgh.utils;

public class GLFrustum {

    public GLFrustum() {
        setOrthographic(-1, 1, -1, 1, -1, 1);
    }

    // Set the View Frustum
    public GLFrustum(float fFov, float fAspect, float fNear, float fFar) {
        setPerspective(fFov, fAspect, fNear, fFar);
    }

    public GLFrustum(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax) {
        setOrthographic(xMin, xMax, yMin, yMax, zMin, zMax);
    }

    public void setOrthographic(float xmin, float xmax, float ymin, float ymax, float zmin,
            float zmax) {
        projMatrix.loadOrtho(xmin, xmax, ymin, ymax, zmin, zmax);

        nearUL.copy(xmin, ymax, zmin);
        nearLL.copy(xmin, ymin, zmin);
        nearUR.copy(xmax, ymax, zmin);
        nearLR.copy(xmax, ymin, zmin);

        farUL.copy(xmin, ymax, zmax);
        farLL.copy(xmin, ymin, zmax);
        farUR.copy(xmax, ymax, zmax);
        farLR.copy(xmax, ymin, zmax);
    }

    public void setPerspective(float fovy, float aspect, float near, float far) {
        float xmin, xmax, ymin, ymax; // Dimensions of near clipping plane
        float xFmin, xFmax, yFmin, yFmax; // Dimensions of far clipping plane

        ymax = (float) (near * Math.tan(fovy * Math.PI / 360.0));
        ymin = -ymax;
        xmin = ymin * aspect;
        xmax = -xmin;
        projMatrix.loadFrustum(xmin, xmax, ymin, ymax, near, far);

        yFmax = (float) (far * (Math.tan(fovy * Math.PI / 360.0)));
        yFmin = -yFmax;
        xFmin = yFmin * aspect;
        xFmax = -xFmin;

        nearUL.copy(xmin, ymax, -near);
        nearLL.copy(xmin, ymin, -near);
        nearUR.copy(xmax, ymax, -near);
        nearLR.copy(xmax, ymin, -near);

        farUL.copy(xFmin, yFmax, -far);
        farLL.copy(xFmin, yFmin, -far);
        farUR.copy(xFmax, yFmax, -far);
        farLR.copy(xFmax, yFmin, -far);

    }

    public Matrix44f getProjectionMatrix() {
        return projMatrix;
    }

    // Calculates the corners of the Frustum and sets the projection matrix.
    // Orthographics Matrix Projection

    // Calculates the corners of the Frustum and sets the projection matrix.
    // Perspective Matrix Projection

    // Builds a transformation matrix and transforms the corners of the Frustum,
    // then derives the plane equations
    public void transform(GLFrame camera) {

        // Create the transformation matrix. This was the trickiest part
        // for me. The default view from OpenGL is down the negative Z
        // axis. However, building a transformation axis from these
        // directional vectors points the frustum the wrong direction. So
        // You must reverse them here, or build the initial frustum
        // backwards - which to do is purely a matter of taste. I chose to
        // compensate here to allow better operability with some of my other
        // legacy code and projects. RSW
        Vector3f vForward = camera.getForwardVector();
        vForward.x = -vForward.x;
        vForward.y = -vForward.y;
        vForward.z = -vForward.z;
        // /////////////////////////////////////////////////////////////////

        Vector3f vUp = camera.getUpVector();
        Vector3f vOrigin = camera.getOrigin();

        // Calculate the right side (x) vector
        Vector3f xVector = Vector3f.crossProduct(vUp, vForward);

        // Workspace
        Matrix44f rotMat = new Matrix44f();
        rotMat.setMatrixColumn44(xVector, 0);
        rotMat.setMatrixColumn44(vUp, 1);
        rotMat.setMatrixColumn44(vForward, 2);
        rotMat.setMatrixColumn44(vOrigin, 3);

        // //////////////////////////////////////////////////
        // Transform the frustum corners
        Math3D.transformVector3(nearULT, nearUL, rotMat);
        Math3D.transformVector3(nearLLT, nearLL, rotMat);
        Math3D.transformVector3(nearURT, nearUR, rotMat);
        Math3D.transformVector3(nearLRT, nearLR, rotMat);
        Math3D.transformVector3(farULT, farUL, rotMat);
        Math3D.transformVector3(farLLT, farLL, rotMat);
        Math3D.transformVector3(farURT, farUR, rotMat);
        Math3D.transformVector3(farLRT, farLR, rotMat);

        // //////////////////////////////////////////////////
        // Derive Plane Equations from points... Points given in
        // counter clockwise order to make normals point inside
        // the Frustum
        // Near and Far Planes
        Math3D.getPlaneEquation(nearPlane, nearULT, nearLLT, nearLRT);
        Math3D.getPlaneEquation(farPlane, farULT, farURT, farLRT);

        // Top and Bottom Planes
        Math3D.getPlaneEquation(topPlane, nearULT, nearURT, farURT);
        Math3D.getPlaneEquation(bottomPlane, nearLLT, farLLT, farLRT);

        // Left and right planes
        Math3D.getPlaneEquation(leftPlane, nearLLT, nearULT, farULT);
        Math3D.getPlaneEquation(rightPlane, nearLRT, farLRT, farURT);
    }

    // Allow expanded version of sphere test
    public boolean testSphere(float x, float y, float z, float fRadius) {
        Vector3f vPoint = new Vector3f(x, y, z);

        return testSphere(vPoint, fRadius);
    }

    // Test a point against all frustum planes. A negative distance for any
    // single plane means it is outside the frustum. The radius value allows
    // to test for a point (radius = 0), or a sphere. Possibly there might
    // be some gain in an alternative function that saves the addition of
    // zero in this case.
    // Returns false if it is not in the frustum, true if it intersects
    // the Frustum.
    public boolean testSphere(Vector3f vPoint, float fRadius) {
        float fDist;

        // Near Plane - See if it is behind me
        fDist = Math3D.getDistanceToPlane(vPoint, nearPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        // Distance to far plane
        fDist = Math3D.getDistanceToPlane(vPoint, farPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        fDist = Math3D.getDistanceToPlane(vPoint, leftPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        fDist = Math3D.getDistanceToPlane(vPoint, rightPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        fDist = Math3D.getDistanceToPlane(vPoint, bottomPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        fDist = Math3D.getDistanceToPlane(vPoint, topPlane);
        if (fDist + fRadius <= 0.0)
            return false;

        return true;
    }

    protected Matrix44f projMatrix = new Matrix44f();

    // Untransformed corners of the frustum
    protected Vector3f nearUL = new Vector3f();

    protected Vector3f nearLL = new Vector3f();

    protected Vector3f nearUR = new Vector3f();

    protected Vector3f nearLR = new Vector3f();

    protected Vector3f farUL = new Vector3f();

    protected Vector3f farLL = new Vector3f();

    protected Vector3f farUR = new Vector3f();

    protected Vector3f farLR = new Vector3f();

    // Transformed corners of Frustum
    protected Vector3f nearULT = new Vector3f();

    protected Vector3f nearLLT = new Vector3f();

    protected Vector3f nearURT = new Vector3f();

    protected Vector3f nearLRT = new Vector3f();

    protected Vector3f farULT = new Vector3f();

    protected Vector3f farLLT = new Vector3f();

    protected Vector3f farURT = new Vector3f();

    protected Vector3f farLRT = new Vector3f();

    // Base and Transformed plane equations
    protected Vector4f nearPlane = new Vector4f();

    protected Vector4f farPlane = new Vector4f();

    protected Vector4f leftPlane = new Vector4f();

    protected Vector4f rightPlane = new Vector4f();

    protected Vector4f topPlane = new Vector4f();

    protected Vector4f bottomPlane = new Vector4f();
}
